#include "byte_stream.hh"

#include <algorithm>
#include <stdexcept>

using namespace std;

ByteStream::ByteStream(const size_t capacity) : _capacity(capacity) {}

size_t ByteStream::write(const string &data) {
    if (_available_num > _capacity) {
        throw runtime_error("exceed the ByteStream capacity");
    }
    // buffer is full
    if (_available_num == _capacity) {
        return 0;
    }
    // max byte num can be written
    size_t count = min(data.size(), remaining_capacity());
    _buffer.push_back(data.substr(0, count));
    _available_num += count;
    _written_num += count;
    return count;
}

//! \param[in] len bytes will be copied from the output side of the buffer
string ByteStream::peek_output(const size_t len) const {
    string ret{};
    size_t count = min(_available_num, len);
    int i = 0;
    while (count > 0) {
        string temp = _buffer.at(i);
        if (temp.size() > count) {
            ret.append(temp.begin(), temp.begin() + count);
            break;
        } else {
            // temp.size() <= count
            ret.append(temp);
            count -= temp.size();
        }
        ++i;
    }
    return ret;
}

//! \param[in] len bytes will be removed from the output side of the buffer
void ByteStream::pop_output(const size_t len) {
    size_t count = min(_available_num, len);
    _read_num += count;
    _available_num -= count;
    while (count > 0) {
        string &front = _buffer.front();
        if (front.size() > count) {
            string rest{front.substr(count)};
            _buffer.pop_front();
            _buffer.push_front(rest);
            break;
        } else {
            // front.size() <= count
            count -= front.size();
            _buffer.pop_front();
        }
    }
}

//! Read (i.e., copy and then pop) the next "len" bytes of the stream
//! \param[in] len bytes will be popped and returned
//! \returns a string
std::string ByteStream::read(const size_t len) {
    string ret{};
    size_t count = min(_available_num, len);
    _read_num += count;
    _available_num -= count;
    while (count > 0) {
        string &front = _buffer.front();
        if (front.size() > count) {
            ret.append(front.begin(), front.begin() + count);
            string rest{front.substr(count)};
            _buffer.pop_front();
            _buffer.push_front(rest);
            break;
        } else {
            ret.append(front);
            count -= front.size();
            _buffer.pop_front();
        }
    }
    return ret;
}

void ByteStream::end_input() { _input_ended = true; }

bool ByteStream::input_ended() const { return _input_ended; }

size_t ByteStream::buffer_size() const { return _available_num; }

bool ByteStream::buffer_empty() const { return _available_num == 0; }

bool ByteStream::eof() const { return (buffer_empty() && _input_ended); }

size_t ByteStream::bytes_written() const { return _written_num; }

size_t ByteStream::bytes_read() const { return _read_num; }

size_t ByteStream::remaining_capacity() const { return {_capacity - _available_num}; }
